﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    #region class SearchEngine : engine pro řízení hledání
    /// <summary>
    /// Engine pro řízení hledání
    /// </summary>
    public class SearchEngine
    {
        #region Konstrukce, proměnné
        internal SearchEngine(TraceDocumentForm document, ISearchProvider provider, SearchEnginePanel searchPanel)
        {
            this._TraceDocumentForm = document;
            this._Main = document.DataSource.Main;
            this._TraceDocument = document.Document;
            this._SearchProvider = provider;
            this._SearchPanel = searchPanel;
            this._SearchControl = searchPanel.SearchControl;
            this._ProgressPartial = searchPanel.ProgressBar.ProgressPartial;
        }
        private TraceDocumentForm _TraceDocumentForm;
        private MainInfo _Main;
        private TraceDocument _TraceDocument;
        private ISearchProvider _SearchProvider;
        private SearchEnginePanel _SearchPanel;
        private Control _SearchControl;
        private ProgressPartial _ProgressPartial;
        private bool _SearchInProcesses;
        private SearchProviderScanTraceFileMode _SearchInTraceFiles;
        /// <summary>
        /// Stav procesu hledání, fyzicky je uložen ve vizuálním panelu
        /// </summary>
        internal SearchState CurrentState { get { return this._SearchPanel.CurrentState; } }
        /// <summary>
        /// Počet událostí, po jejichž nalezení dojde k pozastavení vyhledávače.
        /// </summary>
        internal int SearchItemsCount
        {
            get { return this._SearchPanel.SearchItemsCount; }
            set { this._SearchPanel.SearchItemsCount = value; }
        }
        #endregion
        #region Metody pro vyhledávání
        /// <summary>
        /// Vstupní bod hledání.
        /// Spouští se v GUI vláknu.
        /// </summary>
        internal void Run()
        {
            if (this._Prepare())
                WorkingThread.AddItem(this._Run, this._Done);
            else
                this._Done();
        }
        /// <summary>
        /// Příprava hledacího providera (jeho metoda Prepare()).
        /// Provider si ověří správnost zadání, ohlásí chyby. Pokud je OK, pak nastaví hledané oblasti (do args: EnableSearchProcesses a EnableSearchTraceFiles).
        /// Spouští se v GUI vláknu.
        /// </summary>
        /// <returns></returns>
        private bool _Prepare()
        {
            bool result = false;
            SearchProviderPrepareArgs args = new SearchProviderPrepareArgs(this._TraceDocument, this._SearchControl);
            try
            {
                this._SearchProvider.PrepareToSearch(args);
                this._SearchInProcesses = args.EnableSearchProcesses;
                this._SearchInTraceFiles = args.EnableSearchTraceFiles;
                result = true;
            }
            catch (Exception exc)
            {
                Dialog.ShowInfo(exc.Message);
            }
            this.SearchItemsCount = Steward.AppConfig.SearchItemsCount;
            return result;
        }
        /// <summary>
        /// Vlastní procedura hledání.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _Run()
        {
            bool searchInProcesses = this._SearchInProcesses;
            bool searchInTraceFiles = (this._SearchInTraceFiles == SearchProviderScanTraceFileMode.ScanAllFiles || this._SearchInTraceFiles == SearchProviderScanTraceFileMode.ScanOnlyNoProcessedFiles);
            bool progressBooth = (searchInProcesses && searchInTraceFiles);
            decimal progressEdge = (progressBooth ? this._DetectProgressEdge() : 0m);
            if (searchInProcesses)
            {
                ProgressPartial progress = this._ProgressPartial.CreateSubProgress(0.0m, (progressBooth ? progressEdge : 1.0m));
                this._RunProcesses(progress);
            }
            if (searchInTraceFiles)
            {
                ProgressPartial progress = this._ProgressPartial.CreateSubProgress((progressBooth ? progressEdge : 0.0m), 1.0m);
                this._RunTraceFiles(progress);
            }
        }
        /// <summary>
        /// Určí, kde se bude nacházet hrana mezi progresem vyjadřujícím hledání v paměti a hledání v trace files
        /// </summary>
        /// <returns></returns>
        private decimal _DetectProgressEdge()
        {
            if (this._Main.Traces.Count == 0) return 1m;

            // Určím velikost všech trace souborů, a velikost těch souborů které budeme načítat:
            long allSize = this._Main.Traces.List.Sum(t => t.FileSize);
            long scanSize = this._GetTraceFileForScan(this._SearchInTraceFiles).Sum(t => t.FileSize);
            if (scanSize == 0L) return 1m;
            if (scanSize == allSize) return 0m;

            // Zde máme určit poměrné místo ukazatele (0 - 1), kde bude přechod z bloku načítání do paměti do bloku scanování souborů.
            // Víme, že několik trace souborů budeme načítat, ale ne všechny.
            // Soudíme, že scanování dat pocházejících z jednoho nenačteného souboru bude trvat 8x déle, než projít tatáž data v paměti.
            // Z toho tedy lze vypočítat, že čas scanování dat z paměti bude:
            decimal a = allSize - scanSize;      // To uz je načteno
            decimal b = 8m * scanSize;           // To budeme načítat, bude to trvat 8x déle než to co už je hotovo
            decimal r = a / (a + b);             // Poměr = a (už načteno) / (a + b (=celek ))
            return r;
        }
        /// <summary>
        /// Vrátí seznam trace souborů vyhovujících podmínce (parametr scanMode) 
        /// a vyhovujících provideru (metoda CanSearchInTraceFile()).
        /// </summary>
        /// <param name="scanMode"></param>
        /// <returns></returns>
        private List<TraceInfo> _GetTraceFileForScan(SearchProviderScanTraceFileMode scanMode)
        {
            List<TraceInfo> result = new List<TraceInfo>();
            foreach (TraceInfo trace in this._Main.Traces.List)
            {
                bool add = false;
                SearchProviderDetectTraceArgs args = new SearchProviderDetectTraceArgs(this._TraceDocument, this._SearchControl, trace);

                switch (scanMode)
                {
                    case SearchProviderScanTraceFileMode.NoSearch:
                        break;
                    case SearchProviderScanTraceFileMode.ScanAllFiles:
                        add = this._SearchProvider.CanSearchInTraceFile(args);
                        break;
                    case SearchProviderScanTraceFileMode.ScanOnlyNoProcessedFiles:
                        if (!trace.ContentProcessed)
                            add = this._SearchProvider.CanSearchInTraceFile(args);
                        break;
                }
                if (add)
                    result.Add(trace);
            }
            return result;
        }
        /// <summary>
        /// Vlastní procedura hledání v paměti. Prochází všechny procesy.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _RunProcesses(ProgressPartial progress)
        {
            List<ProcessInfo> processes = this._Main.Processes.ToList();
            int count = processes.Count;
            progress.ValueFrom = 0m;
            progress.ValueTo = count;
            for (int i = 0; i < count; i++)
            {
                if (this._IsStateBreak()) break;

                ProcessInfo process = processes[i];
                if (process.Events.Count > 0)
                {
                    SearchProviderDetectProcessArgs args = new SearchProviderDetectProcessArgs(this._TraceDocument, this._SearchControl, process);
                    if (_SearchProvider.CanSearchInProcess(args))
                    {
                        ProgressPartial subProgress = progress.CreateSubProgress(i, i + 1);
                        this._RunProcessOne(process, subProgress);
                    }
                }
                progress.Value = (i + 1);
            }
        }
        /// <summary>
        /// Vlastní procedura hledání v paměti. Pro jeden proces prochází všechny události.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _RunProcessOne(ProcessInfo process, ProgressPartial progress)
        {
            List<EventInfo> events = process.Events.List;
            int count = events.Count;
            progress.ValueFrom = 0m;
            progress.ValueTo = count;
            for (int i = 0; i < count; i++)
            {
                if ((i & 0x03FF) == 0)
                {   // 1x za 1024 událostí se podívám na stav (pause/stop/run). Nebudu to volat po každé jednotlivé události, je jich statisíce až desetimiliony.
                    if (this._IsStateBreak()) break;
                    // Rovněž tak progres budu řešit až po 1/1024 událostí:
                    progress.Value = i;
                }

                EventInfo eventInfo = events[i];
                if (_SearchProvider.TestEvent(new SearchProviderTestEventArgs(this._TraceDocument, this._SearchControl, process, eventInfo)))
                    this._SearchPanel.AddResultItem(eventInfo);
            }
            progress.Value = count - 1;                // Dokončení progresu
        }
        /// <summary>
        /// Vlastní procedura hledání v souborech.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _RunTraceFiles(ProgressPartial progress)
        {
            List<TraceInfo> traces = this._GetTraceFileForScan(this._SearchInTraceFiles);
            int count = traces.Count;
            if (traces.Count == 0) return;
            if (traces.Count > 1)
                traces.Sort((a, b) => a.Time.Begin.CompareTo(b.Time.Begin));

            decimal position = 0m;
            progress.ValueFrom = position;
            progress.ValueTo = traces.Sum(t => t.FileSize);

            for (int i = 0; i < count; i++)
            {
                if (this._IsStateBreak()) break;

                TraceInfo trace = traces[i];
                SearchProviderDetectTraceArgs args = new SearchProviderDetectTraceArgs(this._TraceDocument, this._SearchControl, trace);
                if (this._SearchProvider.CanSearchInTraceFile(args))
                {
                    ProgressPartial subProgress = progress.CreateSubProgress(position, position + trace.FileSize);
                    this._RunTraceFileOne(trace, subProgress);
                }
                position += trace.FileSize;
                progress.Value = position;
            }
        }
        /// <summary>
        /// Vlastní procedura hledání v souboru trace. Scanuje daný sobor.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _RunTraceFileOne(TraceInfo trace, ProgressPartial progress)
        {
            trace.ScanContent(this._RunTraceFileItem);
        }
        /// <summary>
        /// TraceReader v roli scannera prochází soubor, zpracovává jej na jednotlivé položky (události), a ty posílá sem.
        /// Zdejší metoda pak zajistí testování, zda daná položka vyhovuje filtru, a pokud ano pak ji zařadí do Result listu.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="trace"></param>
        private void _RunTraceFileItem(object data, TraceInfo trace)
        {
            ITextGraphPointer graphPointer = _SearchProvider.TestObject(new SearchProviderTestTraceDataArgs(this._TraceDocument, this._SearchControl, trace, data));
            if (graphPointer != null)
                this._SearchPanel.AddResultItem(graphPointer);
        }
        /// <summary>
        /// Detekuje stav hledání (Start - Pause - Stop).
        /// Pokud je Start, vrací false.
        /// Pokud je Pause, nevrací řízení.
        /// Pokud je Stop, vrací true.
        /// </summary>
        /// <returns></returns>
        private bool _IsStateBreak()
        {
            while (true)
            {
                SearchState state = this._SearchPanel.CurrentState;
                switch (state)
                {
                    case SearchState.Running: return false;
                    case SearchState.None: return true;
                }
                System.Threading.Thread.Sleep(75);
            }
        }
        /// <summary>
        /// Po doběhnutí hledání.
        /// Spouští se ve Working vláknu.
        /// </summary>
        private void _Done()
        {
            this._SearchPanel.SearchDone();
        }
        /// <summary>
        /// Zajistí aktivaci dané události
        /// </summary>
        /// <param name="eventInfo"></param>
        internal void ActivateFoundEventItem(IGraphPointer graphPointer)
        {
            TraceDocumentForm traceDocForm = this._TraceDocumentForm;
            if (traceDocForm != null)
                traceDocForm.GraphPointerAccept(graphPointer, true);
        }
        #endregion
        #region Tvorba ITextGraphPointer z dat události EventInfo (static metody public i private)
        /// <summary>
        /// Vytvoří a vrátí ukazatel ITextGraphPointer z dat události EventInfo.
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        public static ITextGraphPointer CreateITextGraphPointer(EventInfo eventInfo)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();

            IDataElement element = eventInfo as IDataElement;
            pointer.Info = element.TimeRange.Seconds.ToString("### ##0.000").Trim();   // (element.TimeRange.Seconds <= 0d ? element.TimeRange.Begin.ToString("HH:mm:ss.fff") : element.TimeRange.Seconds.ToString("### ##0.000").Trim());
            pointer.Text = CreatePointerGetCaption(element);
            pointer.TooltipText = element.ToolTipInfo;

            IGraphPointer ptr = CreateIGraphPointer(eventInfo);
            pointer.RowFilter = ptr.RowFilter;
            pointer.ExpandedNodes.AddRange(ptr.ExpandedNodes);
            pointer.FirstVisibleNode = ptr.FirstVisibleNode;
            pointer.FirstVisiblePixel = ptr.FirstVisiblePixel;
            pointer.Time = ptr.Time;

            return pointer;
        }
        /// <summary>
        /// Vytvoří a vrátí ukazatel ITextGraphPointer z dodaných dat
        /// </summary>
        /// <param name="infoTime"></param>
        /// <param name="text"></param>
        /// <param name="toolTipInfo"></param>
        /// <param name="rowFilter"></param>
        /// <param name="expandedNodes"></param>
        /// <param name="firstVisibleNode"></param>
        /// <param name="firstVisiblePixel"></param>
        /// <param name="selectedNode"></param>
        /// <returns></returns>
        public static ITextGraphPointer CreateITextGraphPointer(TimeRange infoTime, string text, string toolTipInfo,
            RowFilter rowFilter, IEnumerable<string> expandedNodes, 
            string firstVisibleNode, int firstVisiblePixel, string selectedNode)
        {
            string info = infoTime.Seconds.ToString("### ##0.000").Trim();
            TimeRange time = TimeAxisCalcCls.AlignTimeToAxis(infoTime, true);
            return CreateITextGraphPointer(info, text, toolTipInfo,
                time, rowFilter, expandedNodes, 
                firstVisibleNode, firstVisiblePixel, selectedNode);
        }
        /// <summary>
        /// Vytvoří a vrátí ukazatel ITextGraphPointer z dodaných dat
        /// </summary>
        /// <param name="info"></param>
        /// <param name="text"></param>
        /// <param name="toolTipInfo"></param>
        /// <param name="time"></param>
        /// <param name="rowFilter"></param>
        /// <param name="expandedNodes"></param>
        /// <param name="firstVisibleNode"></param>
        /// <param name="firstVisiblePixel"></param>
        /// /// <param name="selectedNode"></param>
        /// <returns></returns>
        public static ITextGraphPointer CreateITextGraphPointer(string info, string text, string toolTipInfo, 
            TimeRange time, RowFilter rowFilter, IEnumerable<string> expandedNodes, 
            string firstVisibleNode, int firstVisiblePixel, string selectedNode)
        {
            TraceDocTextPointer pointer = new TraceDocTextPointer();

            pointer.Info = info;
            pointer.Text = text;
            pointer.TooltipText = toolTipInfo;

            pointer.Time = time;
            pointer.RowFilter = rowFilter;
            pointer.ExpandedNodes.AddRange(expandedNodes);
            pointer.FirstVisibleNode = firstVisibleNode;
            pointer.FirstVisiblePixel = firstVisiblePixel;
            pointer.SelectedNode = selectedNode;

            return pointer;
        }
        /// <summary>
        /// Vrátí nejdelší text z element.Captions
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        internal static string CreatePointerGetCaption(IDataElement element)
        {
            if (element == null || element.Captions == null) return "";
            string caption = "";
            foreach (string c in element.Captions)
            {
                if (c != null && c.Length > caption.Length)
                    caption = c;
            }
            return caption;
        }
        /// <summary>
        /// Vytvoří a vrátí IGraphPointer z dat EventInfo
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        internal static IGraphPointer CreateIGraphPointer(EventInfo eventInfo)
        {
            IDataElement element = eventInfo as IDataElement;
            TraceDocPointer pointer = new TraceDocPointer();
            pointer.RowFilter = null;
            foreach (string targetPath in eventInfo.TargetPaths)
                pointer.ExpandedNodes.AddRange(CreatePointerGetExpandedNodes(eventInfo.TargetHomeNode, targetPath));

            pointer.FirstVisibleNode = CreatePointerGetFirstNode(eventInfo);
            pointer.FirstVisiblePixel = 0;
            pointer.SelectedNode = pointer.FirstVisibleNode;

            pointer.Time = TimeAxisCalcCls.AlignTimeToAxis(element.TimeRange, true);

            return pointer;
        }
        /// <summary>
        /// Vrátí klíč (node) pro první viditelný node daného prvku EventInfo
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        internal static string CreatePointerGetFirstNode(EventInfo eventInfo)
        {
            string path = eventInfo.TargetHomeNode;
            if (String.IsNullOrEmpty(path) && (eventInfo.TargetPaths != null && eventInfo.TargetPaths.Length > 0))
                path = eventInfo.TargetPaths[0];
            if (path == null)
                return "";

            if (path.StartsWith("\\"))
                path = path.Substring(1);

            return path.ToLower();
        }
        /// <summary>
        /// Vrátí sadu klíčů node, které mají být postupně otevřené
        /// </summary>
        /// <param name="homeNode"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        internal static IEnumerable<string> CreatePointerGetExpandedNodes(string homeNode, string path)
        {
            List<string> result = new List<string>();

            TreePath treePath = new TreePath(path);
            if (!treePath.AbsolutePath)
                treePath = new TreePath(homeNode + "\\" + path);

            List<TreePathItem> treeItems = treePath.Items;
            string prev = "";
            for (int i = 0; i < (treeItems.Count - 1); i++)
            {
                string key = treeItems[i].Key.ToLower();
                result.Add(prev + key);
                prev = prev + key + "\\";
            }

            return result;
        }
        #endregion
    }
    #endregion
    #region class SearchEnginePanel : třída panelu, který řídí proces vyhledání událostí
    /// <summary>
    /// Třída panelu, který řídí proces vyhledání událostí. Panel obsahuje nabídku providerů, parametry provideru, buttony Start-Stop, Progress a Result.
    /// </summary>
    internal class SearchEnginePanel : DPanel
    {
        #region Konstrukce
        internal SearchEnginePanel()
        {
            this.InitializeComponents();
        }
        /// <summary>
        /// Tvorba komponent panelu
        /// </summary>
        internal void InitializeComponents()
        {
            this._Persistor = new SearchPersistor();            // Persistor si načte svoje data z XML configu
            this._Providers = Steward.GetServiceObjectList<ISearchProvider>(true).ToList();  // Zjistíme aktuální providery
            this._CurrentProvider = null;

            // _ClosePanelButton
            this._ClosePanelButton = new DButton() { Text = "", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, Size = new Size(22, 20), Image = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.DialogError4_FromFile, FlatStyle = FlatStyle.Flat, Tag = ENABLED_ALLWAYS };
            this._ClosePanelButton.FlatAppearance.BorderSize = 0;
            this.ToolTipForControl(this._ClosePanelButton, "Zavřít");
            this._ClosePanelButton.Click += new EventHandler(_ClosePanelButtonClick);
            this.Controls.Add(this._ClosePanelButton);

            // Line1
            this._Line1 = new HLine() { Text = "Hledat", Height = 21 };
            this.Controls.Add(this._Line1);

            // ProviderCombo
            this._ProviderCombo = new DComboBox() { FlatStyle = FlatStyle.Popup };
            foreach (ISearchProvider provider in this._Providers)
            {
                object item = this._ProviderCombo.AddDComboItem(provider.ProviderName, provider, true);
                if (this._Persistor.IsActiveProvider(provider))
                    this._ProviderCombo.SelectedItem = item;
            }
            this._ProviderCombo.DComboBoxChanged += new DComboBoxChangedHandler(_SearchProviderChanged);
            this._ProviderCombo.TabStop = (this._ProviderCombo.Items.Count > 1);
            this.ToolTipForControl(this._ProviderCombo, "Oblast vyhledávání (vyhledávací algoritmus)");
            this.Controls.Add(this._ProviderCombo);

            // Line2
            this._Line2 = new HLine() { Text = "Parametry", Height = 21 };
            this.Controls.Add(this._Line2);

            // PresetCombo
            this._PresetCombo = new DComboBox() { FlatStyle = FlatStyle.Popup, DropDownStyle = ComboBoxStyle.DropDown };
            this._PresetCombo.DComboBoxChanged += new DComboBoxChangedHandler(_PresetComboChanged);
            this.ToolTipForControl(this._PresetCombo, "Předvolby vyhledání, dříve uložené hodnoty");
            this.Controls.Add(this._PresetCombo);

            // PresetSave
            this._PresetSaveButton = new DButton() { Text = "", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, Size = new Size(26, 23), FlatStyle = FlatStyle.Popup, Image = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.Documentsave3_FromFile };
            this.ToolTipForControl(this._PresetSaveButton, "Uložení parametrů hledání");
            this._PresetSaveButton.Click += new EventHandler(_PresetSaveButtonClick);
            this.Controls.Add(this._PresetSaveButton);

            // Konkrétní vyhledávací parametry - konkrétní control vytváří až zvolený provider (ISearchProvider)
            this._SearchControl = null;

            // _Line3
            this._Line3 = new HLine() { Text = "Vyhledání", Height = 21 };
            this.Controls.Add(this._Line3);

            // Buttons
            this._SearchButtonPause = new DButton() { Text = "", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, Size = new Size(30, 26), ImageEnabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackPause4_FromFile, ImageDisabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackPause3_FromFile, FlatStyle = FlatStyle.Popup };
            this.ToolTipForControl(this._SearchButtonPause, "Pozastaví proces vyhledání");
            this._SearchButtonPause.Click += new EventHandler(_SearchButtonPauseClick);
            this.Controls.Add(this._SearchButtonPause);

            this._SearchButtonStart = new DButton() { Text = "", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, Size = new Size(40, 26), ImageEnabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackStart4_FromFile, ImageDisabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackStart3_FromFile, FlatStyle = FlatStyle.Popup };
            this.ToolTipForControl(this._SearchButtonStart, "Zahájí proces vyhledání");
            this._SearchButtonStart.Click += new EventHandler(_SearchButtonStartClick);
            this.Controls.Add(this._SearchButtonStart);

            this._SearchButtonStop = new DButton() { Text = "", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, Size = new Size(30, 26), ImageEnabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackStop4_FromFile, ImageDisabled = Noris.Tools.TraceVisualiser.Pics.PicLibrary16.MediaPlaybackStop3_FromFile, FlatStyle = FlatStyle.Popup };
            this.ToolTipForControl(this._SearchButtonStop, "Zastaví proces vyhledání");
            this._SearchButtonStop.Click += new EventHandler(_SearchButtonStopClick);
            this.Controls.Add(this._SearchButtonStop);

            // Progress
            this._ProgressBar = new DProgressBar() { Size = new Size(100, 12), Visible = false };
            this.Controls.Add(this._ProgressBar);

            // Line4
            this._Line4 = new HLine() { Text = "Výsledky", Height = 21 };
            this.Controls.Add(this._Line4);

            // Výsledky v seznamu
            this._ResultList = new DListView() { Size = new Size(100, 200), View = View.Details, Tag = ENABLED_ALLWAYS };
            this._ResultListInit();
            this.Controls.Add(this._ResultList);

            this._SearchButtonPause.Enabled = false;
            this._SearchButtonStop.Enabled = false;

            // this
            this.Size = new System.Drawing.Size(140, 750);
            this.BackColor = Control.DefaultBackColor;
            this.BorderStyle = BorderStyle.None;
            this.SizeChanged += new EventHandler(_SizeChanged);
            this.Padding = new Padding(5);
            this.TabStop = true;

            // Tooltip style
            this.ToolTipForm.CurrentTooltipMode = TooltipMode.Baloon;
            this.ToolTipForm.ToolTipTitle = "Vyhledávání";
            this.ToolTipForm.ToolTipIcon = ToolTipIcon.Info;

            // Aktivuje první vybraný provider, vytvoří skrze něj vyhledávací control, provede Layout:
            this._SearchProviderChanged();
        }
        // Vizuální controly:
        private SearchPersistor _Persistor;
        private DButton _ClosePanelButton;
        private HLine _Line1;
        private DComboBox _ProviderCombo;
        private DComboBox _PresetCombo;
        private DButton _PresetSaveButton;
        private HLine _Line2;
        private Control _SearchControl;
        private HLine _Line3;
        private DButton _SearchButtonPause;
        private DButton _SearchButtonStart;
        private DButton _SearchButtonStop;
        private DProgressBar _ProgressBar;
        private HLine _Line4;
        private DListView _ResultList;
        // Nevizuální data a příznaky:
        private List<ISearchProvider> _Providers;
        private ISearchProvider _CurrentProvider;
        private bool _HasSearchControl { get { return (this._SearchControl != null); } }
        private bool _HasCurrentProvider { get { return (this._CurrentProvider != null); } }
        private bool _IsDataPersistent { get; set; }
        #endregion
        #region Layout
        /// <summary>
        /// Po změně velikosti this
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _SizeChanged(object sender, EventArgs e)
        {
            if (!this._LayoutArrangeProcessing)
                this._LayoutArrange();
        }
        /// <summary>
        /// Rozmístí svoje prvky přiměřeně k velikosti controlu this._SearchControl
        /// </summary>
        private void _LayoutArrange()
        {
            if (this._LayoutArrangeProcessing) return;

            try
            {
                this._LayoutArrangeProcessing = true;

                Size searchSize = (this._HasSearchControl ? this._SearchControl.Size : new Size(160, 380));
                if (searchSize.Width < 130) searchSize.Width = 130;
                if (searchSize.Width > 360) searchSize.Width = 360;

                Padding padding = this.Padding;
                int y = padding.Top;
                int x = padding.Left;
                int w = searchSize.Width;
                int c = x + w / 2;
                int mw = this._PresetSaveButton.Width;

                this._ClosePanelButton.Location = new Point(x + w - this._ClosePanelButton.Width, y);
                y += 7;

                this._Line1.Bounds = new Rectangle(x, y, w, this._Line1.Height);
                y = this._Line1.Bottom + 3;

                this._ProviderCombo.Bounds = new Rectangle(x, y, w, this._ProviderCombo.Height);
                y = this._ProviderCombo.Bottom + 3;

                this._Line2.Bounds = new Rectangle(x, y, w, this._Line2.Height);
                y = this._Line2.Bottom + 3;

                if (this._IsDataPersistent)
                {
                    this._PresetCombo.Bounds = new Rectangle(x, y, w - mw - 2, this._PresetCombo.Height);
                    this._PresetSaveButton.Bounds = new Rectangle(this._PresetCombo.Right + 2, y, mw, this._PresetCombo.Height);
                    if (!this._PresetCombo.Visible) this._PresetCombo.Visible = true;
                    if (!this._PresetSaveButton.Visible) this._PresetSaveButton.Visible = true;
                    y = this._PresetCombo.Bottom + 3;
                }
                else
                {
                    if (this._PresetCombo.Visible) this._PresetCombo.Visible = false;
                    if (this._PresetSaveButton.Visible) this._PresetSaveButton.Visible = false;
                }

                if (this._SearchControl != null)
                {
                    this._SearchControl.Bounds = new Rectangle(x, y, w, this._SearchControl.Height);
                    y = this._SearchControl.Bottom + 3;
                }

                this._Line3.Bounds = new Rectangle(x, y, w, this._Line3.Height);
                y = this._Line3.Bottom + 3;

                int sw = this._SearchButtonStart.Width / 2;
                this._SearchButtonPause.Location = new Point(c - sw - 3 - this._SearchButtonPause.Width, y);
                this._SearchButtonStart.Location = new Point(c - sw, y);
                this._SearchButtonStop.Location = new Point(c + sw + 3, y);
                y = this._SearchButtonStart.Bottom + 3;

                this._ProgressBar.Bounds = new Rectangle(x, y, w, this._ProgressBar.Height);
                y = this._ProgressBar.Bottom + 3;

                this._Line4.Bounds = new Rectangle(x, y, w, this._Line4.Height);
                y = this._Line4.Bottom + 3;

                int h = this._HeightDisponible - y - padding.Bottom - 10;
                if (h < 80) h = 80;
                this._ResultList.Bounds = new Rectangle(x, y, w, h);
                y = this._ResultList.Bottom + 3;

                int tw = w + padding.Horizontal;
                int th = y + padding.Bottom;
                if (this.Width != tw || this.Height != th)
                    this.Size = new Size(tw, th);

                this.OnLayoutArrangedAfter();
            }
            finally
            {
                this._LayoutArrangeProcessing = false;
            }
        }
        private bool _LayoutArrangeProcessing;
        #endregion
        #region private eventhandlery
        #region změna providera
        /// <summary>
        /// Po změně vybrané položky v combo boxu providera
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _SearchProviderChanged(object sender, DComboBoxChangedArgs e)
        {
            this._SearchProviderChanged();
        }
        /// <summary>
        /// Po změně vybrané položky v combo boxu providera
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _SearchProviderChanged()
        {
            var item = this._ProviderCombo.SelectedDItem;
            ISearchProvider provider = ((item == null || item.Data == null) ? (ISearchProvider)null : item.Data as ISearchProvider);
            this._SearchProviderSelect(provider);
        }
        /// <summary>
        /// Vybrat daného providera
        /// </summary>
        /// <param name="provider"></param>
        private void _SearchProviderSelect(string providerName)
        {
            this._ProviderCombo.SelectDItem(providerName);
        }
        /// <summary>
        /// Po výběru providera
        /// </summary>
        /// <param name="provider"></param>
        private void _SearchProviderSelect(ISearchProvider provider)
        {
            this._CurrentProvider = provider;
            this._Persistor.ActiveProvider = provider;
            this._SearchProviderSelectAfter();
        }
        /// <summary>
        /// Po výběru providera
        /// </summary>
        /// <param name="provider"></param>
        private void _SearchProviderSelectAfter()
        {
            ISearchProvider provider = this._CurrentProvider;

            this._PresetCombo.Items.Clear();
            if (this._SearchControl != null)
                this.Controls.Remove(this._SearchControl);
            this._SearchControl = null;
            this._IsDataPersistent = false;

            if (provider == null)
            {   // clear:

            }
            else
            {   // set:
                this._SearchControl = provider.CreateSearchControl();
                if (this._SearchControl != null)
                {
                    this._SearchControl.SizeChanged += new EventHandler(_SearchControl_SizeChanged);
                    this.Controls.Add(this._SearchControl);
                    this._IsDataPersistent = (this._SearchControl is IDataPersistent);
                }
            }
            if (this._IsDataPersistent)
                this._PresetComboFill();

            this._LayoutArrange();
        }
        /// <summary>
        /// Poté, kdy vyhledávací control sám změní velikost - přizpůsobíme se tomu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _SearchControl_SizeChanged(object sender, EventArgs e)
        {
            this._LayoutArrange();
        }
        #endregion
        #region presety
        /// <summary>
        /// Naplní položky do combo _PresetCombo. Je voláno po změně providera, tehdy když provider existuje a umožňuje persistenci (IDataPersistent).
        /// </summary>
        private void _PresetComboFill()
        {
            this._PresetComboFill(null, false);
        }
        /// <summary>
        /// Naplní položky combo boxu Presety, položky získá z persistoru pro aktuálního providera.
        /// Může aktivovat řádek daného názvu, a podle požadavku showData pak dojde k načtení jeho dat do Search controlu.
        /// </summary>
        /// <param name="presetName"></param>
        /// <param name="showData"></param>
        private void _PresetComboFill(string presetName, bool showData)
        {
            if (!this._Persistor.HasActiveProviderInfo) return;
            this._PresetCombo.DComboBoxChangedDisabled = true;
            this._PresetCombo.FillDComboItems(this._Persistor.ActiveProviderPresetItems, presetName);
            this._PresetCombo.DComboBoxChangedDisabled = false;
            if (showData)
                this._PresetLoad(this._PresetCombo.SelectedDItem);
        }
        /// <summary>
        /// Změna v combo boxu Preset. Reagujeme pouze na změnu typu ItemChange.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _PresetComboChanged(object sender, DComboBoxChangedArgs e)
        {   // Sem se dostane řízení jednak po změně vybrané položky, a rovněž po změně textu vepsaného do položky. Detekovat druh změny je možno podle e.ChangeMode.
            if (e.ChangeMode == DComboBoxChangeMode.ItemChange)
            {
                this._PresetLoad(e.Item);
            }
        }
        /// <summary>
        /// Klik na button Save preset
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _PresetSaveButtonClick(object sender, EventArgs e)
        {
            this._PresetSave();
        }
        /// <summary>
        /// Zajistí zobrazení dat uložených v položce nabídky Combo.
        /// V položce jsou uložena Data, která představují obsah presetu.
        /// </summary>
        /// <param name="dItem"></param>
        private void _PresetLoad(DComboItem dItem)
        {
            if (dItem == null) return;
            IDataPersistent dataPersistent = this._SearchControl as IDataPersistent;
            if (dataPersistent == null) return;
            dataPersistent.PersistentData = SearchPersistor.GetPresetData(dItem);
        }
        /// <summary>
        /// Uloží aktuální obsah jako preset (název + data).
        /// Pokud název existuje, přepíše se. Pokud neexistuje, založíme nový.
        /// </summary>
        private void _PresetSave()
        {
            string presetName = this._PresetCombo.Text;
            if (String.IsNullOrEmpty(presetName)) return;
            IDataPersistent dataPersistent = this._SearchControl as IDataPersistent;
            if (dataPersistent == null) return;
            XElement presetData = dataPersistent.PersistentData;
            this._Persistor.SetPreset(presetName, presetData);
            this._PresetComboFill(presetName, false);
        }
        #endregion
        #region close, start, pause, stop
        void _ClosePanelButtonClick(object sender, EventArgs e)
        {
            this.OnHidePanel();
        }
        void _SearchButtonStartClick(object sender, EventArgs e)
        {
            this._SearchRun();
        }
        void _SearchButtonPauseClick(object sender, EventArgs e)
        {
            this._SearchPause();
        }
        void _SearchButtonStopClick(object sender, EventArgs e)
        {
            this._SearchStop();
        }
        #endregion
        #endregion
        #region Public property a eventy
        /// <summary>
        /// Událost, kdy panel sám sebe chce schovat (uživatel kliknul na button Close)
        /// </summary>
        internal event EventHandler HidePanel;
        /// <summary>
        /// Událost, kdy Panel rozmístil svoje interní controly
        /// </summary>
        internal event EventHandler LayoutArrangedAfter;
        /// <summary>
        /// Událost, kdy uživatel dal DoubleClick na událost v Result listu.
        /// Aktální vybraná položka je vždy k dispozici v property this.ResultSelectedEventInfo
        /// </summary>
        internal event EventHandler ResultListDoubleClick;
        protected virtual void OnHidePanel()
        {
            if (this.HidePanel != null)
                this.HidePanel(this, EventArgs.Empty);
        }
        protected virtual void OnLayoutArrangedAfter()
        {
            if (this.LayoutArrangedAfter != null)
                this.LayoutArrangedAfter(this, EventArgs.Empty);
        }
        protected virtual void OnResultListDoubleClick()
        {
            if (this.ResultListDoubleClick != null)
                this.ResultListDoubleClick(this, EventArgs.Empty);
        }
        /// <summary>
        /// Disponibilní výška prvku
        /// </summary>
        public int HeightDisponible
        {
            get { return this._HeightDisponible; }
            set
            {
                this._HeightDisponible = (value < 100 ? 100 : value);
                this._LayoutArrange();
            }
        }
        private int _HeightDisponible;
        /// <summary>
        /// Událost (EventInfo), která je aktuálně vybraná v Result listu.
        /// Může být null.
        /// Nelze setovat.
        /// </summary>
        internal IGraphPointer ResultSelectedGraphPointer
        {
            get
            {
                ListView.SelectedListViewItemCollection svic = this._ResultList.SelectedItems;
                if (svic == null || svic.Count == 0) return null;
                IGraphPointer graphPointer = svic[0].Tag as IGraphPointer;
                return graphPointer;
            }
        }
        /// <summary>
        /// Control, který byl vytvořen providerem hledání a obsahuje jeho filtrační podmínky
        /// </summary>
        internal Control SearchControl { get { return this._SearchControl; } }
        /// <summary>
        /// Progress bar
        /// </summary>
        internal DProgressBar ProgressBar { get { return this._ProgressBar; } }
        #endregion
        #region Result List (Clear, Add, DoubleClick)
        /// <summary>
        /// Vyprázdní seznam ResultList, před zahájením nového hledání
        /// </summary>
        internal void ClearResultList()
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action(this.ClearResultList));
            else
                this._ResultListClear();
        }
        /// <summary>
        /// Do Result listu přidá položku pro danou událost.
        /// </summary>
        /// <param name="eventInfo"></param>
        internal void AddResultItem(EventInfo eventInfo)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<EventInfo>(this._AddResultEventInfo), eventInfo);
            else
                this._AddResultEventInfo(eventInfo);
        }
        /// <summary>
        /// Do Result listu přidá položku pro danou událost.
        /// </summary>
        /// <param name="graphPointer"></param>
        internal void AddResultItem(ITextGraphPointer graphPointer)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<ITextGraphPointer>(this._AddResultGraphPointer), graphPointer);
            else
                this._AddResultGraphPointer(graphPointer);
        }
        /// <summary>
        /// Iniciace seznamu výsledků, volat jen 1x
        /// </summary>
        private void _ResultListInit()
        {
            this._ResultList.Columns.Clear();

            ColumnHeader timeColumn = new ColumnHeader();
            timeColumn.Text = "Time";
            timeColumn.TextAlign = HorizontalAlignment.Center;
            timeColumn.Width = 50;
            timeColumn.Name = "TimeColumn";
            this._ResultList.Columns.Add(timeColumn);

            ColumnHeader infoColumn = new ColumnHeader();
            infoColumn.Text = "Info";
            infoColumn.TextAlign = HorizontalAlignment.Center;
            infoColumn.Width = 150;
            infoColumn.Name = "InfoColumn";
            this._ResultList.Columns.Add(infoColumn);

            this._ResultList.AllowColumnReorder = false;
            this._ResultList.AllowDrop = false;
            this._ResultList.FullRowSelect = true;
            this._ResultList.GridLines = false;
            this._ResultList.HeaderStyle = ColumnHeaderStyle.Nonclickable;
            this._ResultList.HideSelection = false;
            this._ResultList.HotTracking = false; // true;
            this._ResultList.CheckBoxes = false;
            this._ResultList.LabelEdit = false;
            this._ResultList.LabelWrap = false;
            this._ResultList.MultiSelect = false;
            this._ResultList.Scrollable = true;
            this._ResultList.ShowItemToolTips = true;
            this._ResultList.View = View.Details;

            this._ResultList.ClientSizeChanged += new EventHandler(_ResultList_ClientSizeChanged);
            this._ResultList.DoubleClick += new EventHandler(_ResultList_DoubleClick);
        }
        /// <summary>
        /// On doubleclick na Result listu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _ResultList_DoubleClick(object sender, EventArgs e)
        {
            this._ActivateFoundEventItem();
            this.OnResultListDoubleClick();
        }
        /// <summary>
        /// Skrz SearchEngine zajistí aktivaci nalezené události
        /// </summary>
        private void _ActivateFoundEventItem()
        {
            if (this._SearchEngine != null)
            {
                IGraphPointer graphPointer = this.ResultSelectedGraphPointer;
                if (graphPointer != null)
                    this._SearchEngine.ActivateFoundEventItem(graphPointer);
            }
        }
        /// <summary>
        /// After Resize Result listu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _ResultList_ClientSizeChanged(object sender, EventArgs e)
        {
            this._ResultList.Columns[1].Width = this._ResultList.ClientSize.Width - this._ResultList.Columns[0].Width;
        }
        /// <summary>
        /// Vyčistí obsah Result listu
        /// </summary>
        private void _ResultListClear()
        {
            this._ResultList.Items.Clear();
            this._ResultList.Groups.Clear();
            this._SearchEngine = null;
        }
        /// <summary>
        /// Přidá jednu položku (událost) do seznamu Result list
        /// </summary>
        /// <param name="eventInfo"></param>
        private void _AddResultEventInfo(EventInfo eventInfo)
        {
            if (this._CurrentState == SearchState.Paused || this._CurrentState == SearchState.Running)
            {   // Pokud by stav byl None, pak už je vyhledávání zastaveno a příchozí prvky už vkládat nebudeme (přicházejí z threadu na pozadí):
                ListViewItem item = this._CreateResultItem(eventInfo);
                this._AddResultItem(item);
            }
        }
        /// <summary>
        /// Přidá jednu položku (událost) do seznamu Result list
        /// </summary>
        /// <param name="graphPointer"></param>
        private void _AddResultGraphPointer(ITextGraphPointer graphPointer)
        {
            if (this._CurrentState == SearchState.Paused || this._CurrentState == SearchState.Running)
            {   // Pokud by stav byl None, pak už je vyhledávání zastaveno a příchozí prvky už vkládat nebudeme (přicházejí z threadu na pozadí):
                ListViewItem item = this._CreateResultItem(graphPointer);
                this._AddResultItem(item);
            }
        }
        private void _AddResultItem(ListViewItem item)
        {
            int count = this._ResultList.Items.Count + 1;
            int maxCount = this.SearchItemsCount;
            if ((count % maxCount) == 0)
            {
                this.CurrentState = SearchState.Paused;
                string msg = "Již je vyhledáno " + count.ToString() + " událostí." + Environment.NewLine +
                    "Pokud chcete hledat dál, stiskněte tlačítko Pauza." + Environment.NewLine +
                    "Pokud chcete hledání ukončit, stiskněte tlačítko Stop.";
                // this.ToolTipShow(this, msg);
                // this.ToolTipForControl(this._ProgressBar, msg);
                // this.ToolTipShow(this._ProgressBar, msg);
                this.ToolTipForControl(this._ProgressBar, msg);
                this.ToolTipForm.Show(msg, this, this._ProgressBar.Location, 5000);
                // this.ToolTip.ToolTipShow(this, msg , ToolTipIcon.Warning, "Stačí?");
            }
            this._ResultList.Items.Add(item);
        }
        #endregion
        #region Tvorba položky Result Listu z dat EventInfo nebo ITextGraphPointer
        /// <summary>
        /// Vytvoří a vrátí položku do seznamu Result List na základě dat z EventInfo (=data nalezená v paměti)
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        private ListViewItem _CreateResultItem(EventInfo eventInfo)
        {
            ListViewItem item = new ListViewItem();
            IDataElement element = eventInfo as IDataElement;
            item.Text = element.TimeRange.Seconds.ToString("### ##0.000").Trim();
            item.ToolTipText = element.ToolTipInfo;
            item.SubItems.Add(SearchEngine.CreatePointerGetCaption(element));
            item.Tag = SearchEngine.CreateIGraphPointer(eventInfo);
            return item;
        }
        /// <summary>
        /// Vytvoří a vrátí položku do seznamu Result List na základě dat z IGraphPointer graphPointer (=data načtená ze souboru)
        /// </summary>
        /// <param name="graphPointer"></param>
        /// <returns></returns>
        private ListViewItem _CreateResultItem(ITextGraphPointer graphPointer)
        {
            ListViewItem item = new ListViewItem();
            item.Text = (!String.IsNullOrEmpty(graphPointer.Info) ? graphPointer.Info : graphPointer.Time.Seconds.ToString("### ##0.000").Trim());
            item.ToolTipText = graphPointer.TooltipText;
            item.SubItems.Add(graphPointer.Text);
            item.Tag = graphPointer;
            return item;
        }
        #endregion
        #region Podpora pro vyhledání událostí podle dodaného vzorku ("najdi podobné události")
        /// <summary>
        /// Zastaví vyhledávání
        /// </summary>
        internal void StopSearch()
        {
            this._SearchStop();
        }
        /// <summary>
        /// Metoda vyplní vyhledávací panel daty pro hledání, která byla externě zadána.
        /// </summary>
        /// <param name="providerName"></param>
        /// <param name="presetData"></param>
        internal bool SearchFill(string providerName, XElement presetData)
        {
            if (this.CurrentState == SearchState.Running || this.CurrentState == SearchState.Paused) return false;

            if (String.IsNullOrEmpty(providerName) || presetData == null) return false;

            this._ProviderCombo.SelectDItem(i => SearchPersistor.TypeName(i.Data) == providerName);
            if (!this._Persistor.HasActiveProviderInfo) return false;
            if (this._SearchControl == null) return false;

            IDataPersistent dataPersistent = this._SearchControl as IDataPersistent;
            if (dataPersistent == null) return false;

            this._PresetCombo.SelectedDItem = null;
            dataPersistent.PersistentData = presetData;
            return true;
        }
        #endregion
        #region Podpora procesu hledání, interaktivní změny Enabled prvků formuláře
        /// <summary>
        /// Metoda se volá po skončení hledání, volá se z vyhledávacího engine.
        /// </summary>
        internal void SearchDone()
        {
            this._SearchStop();
            // this._SearchEngine = null;           nebudu jej zahazovat, používá se při aktivaci pointeru (result list => document)
        }
        private void _SearchRun()
        {
            TraceDocumentForm activeDocument = _FindTopDocumentForm();
            ISearchProvider activeProvider = (this._CurrentProvider == null ? null : Steward.GetServiceObject<ISearchProvider>(this._CurrentProvider.GetType(), false));
            if (activeDocument == null || activeProvider == null)
                return;

            this.ClearResultList();
            this._ActiveControlSetDisable();
            this.CurrentState = SearchState.Running;

            this._SearchEngine = new SearchEngine(activeDocument, activeProvider, this);
            this._SearchEngine.Run();
        }
        private void _SearchPause()
        {
            this.CurrentState = (this.CurrentState == SearchState.Paused ? SearchState.Running : SearchState.Paused);
        }
        private void _SearchStop()
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action(_SearchStop));
            else
            {
                this._ActiveControlSetEnable();
                this.CurrentState = SearchState.None;
            }
        }
        /// <summary>
        /// Vrací aktivní okno Trace dokumentu (objekt TraceDocumentForm).
        /// </summary>
        /// <returns></returns>
        private TraceDocumentForm _FindTopDocumentForm()
        {
            MainAppForm mainForm = Steward.MainForm as MainAppForm;
            if (mainForm == null) return null;
            return mainForm.TopMostTraceDocument;
        }
        /// <summary>
        /// Nastaví Enabled = false na this a na všechny jeho Childs.
        /// Controly, jejichž Enabled bylo true a je změněno na false ukládá do this._ActiveControls.
        /// </summary>
        private void _ActiveControlSetDisable()
        {
            this._ActiveControls = new List<Control>();
            this._ActiveControlSetDisable(this);
        }
        /// <summary>
        /// Nastaví Enabled = false na daný Control a na všechny jeho Childs
        /// </summary>
        /// <param name="control"></param>
        private void _ActiveControlSetDisable(Control control)
        {
            string tag = control.Tag as string;
            bool skipType = (control is Label || control is HLine);
            bool enabledAllways = (tag != null && tag == ENABLED_ALLWAYS);
            if (control.Enabled && !skipType && !enabledAllways)
            {
                foreach (Control child in control.Controls)
                    this._ActiveControlSetDisable(child);
                if (!(control is Panel))
                {
                    this._ActiveControls.Add(control);
                    control.Enabled = false;
                }
            }
        }
        /// <summary>
        /// Nastaví Enabled = true na všechny Controly, kterým bylo nastaveno false.
        /// </summary>
        private void _ActiveControlSetEnable()
        {
            if (this._ActiveControls != null)
            {
                this._ActiveControls.Reverse();
                foreach (Control control in this._ActiveControls)
                    control.Enabled = true;
            }
            this._ActiveControls = null;
        }
        /// <summary>
        /// Počet událostí, po jejichž nalezení dojde k pozastavení vyhledávače.
        /// </summary>
        internal int SearchItemsCount
        {
            get { return this._SearchItemsCount; }
            set { this._SearchItemsCount = (value < 0 ? 0 : (value > 1000 ? 1000 : value)); }
        }
        private int _SearchItemsCount;
        /// <summary>
        /// Aktuální stav procesu. Změna stavu ovlivní Enabled na buttony.
        /// </summary>
        internal SearchState CurrentState
        {
            get { return this._CurrentState; }
            set
            {
                this._CurrentState = value;
                this._ShowCurrentState();
            }
        }
        /// <summary>
        /// Zobrazí stav this.__CurrentState do hodnot Enabled button _SearchButtonXxx
        /// </summary>
        private void _ShowCurrentState()
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action(_ShowCurrentState));
            else
            {
                SearchState state = this._CurrentState;
                this._SearchButtonPause.Enabled = (state == SearchState.Running || state == SearchState.Paused);
                this._SearchButtonPause.BackColor = (state == SearchState.Paused ? Color.LightYellow : Button.DefaultBackColor);
                this.ToolTipForControl(this._SearchButtonPause, (state == SearchState.Paused ? "Pokračuje v procesu vyhledávání" : "Pozastaví proces vyhledání"));
                this._SearchButtonStart.Enabled = (state == SearchState.None);
                this._SearchButtonStart.BackColor = (state == SearchState.Running ? Color.LightYellow : Button.DefaultBackColor);
                this._SearchButtonStop.Enabled = (state == SearchState.Paused || state == SearchState.Running);
                this._ProgressBar.Visible = (state == SearchState.Paused || state == SearchState.Running);
            }
        }
        private SearchState _CurrentState;
        private SearchEngine _SearchEngine;
        private List<Control> _ActiveControls;
        public const string ENABLED_ALLWAYS = "EnabledAllways";
        #endregion
    }
    #endregion
    #region class SearchPersistor : správce uložených parametrů hledání
    /// <summary>
    /// SearchPersistor : správce uložených parametrů hledání
    /// </summary>
    internal class SearchPersistor
    {
        #region Konstrukce
        internal SearchPersistor()
        {
            this._FileName = System.IO.Path.Combine(Steward.UserAppDataPath, "SearchData.config.xml");
            this._Providers = new List<ProviderInfo>();
            this._FileLoad();
        }
        private string _FileName;
        private List<ProviderInfo> _Providers;
        private ProviderInfo _ActiveProviderInfo;
        private string _ActiveProviderTypeName;
        #endregion
        #region Persistence celého balíku dat z/do souboru
        /// <summary>
        /// Uloží aktuální stav objektu do souboru
        /// </summary>
        internal void Save()
        {
            this._FileSave();
        }
        private void _FileLoad()
        {
            string fileName = this._FileName;
            if (String.IsNullOrEmpty(fileName)) return;
            if (!System.IO.File.Exists(fileName)) return;
            XDocument xDoc = XDocument.Load(fileName);

            // Hlavička:
            XElement xRoot = xDoc.Element("search_presets");
            if (xRoot == null) return;

            string version = xRoot.Attribute("version").Value;
            this._ActiveProviderTypeName = xRoot.Attribute("active").Value;

            // Jednotlivé presety:
            this._Providers.Clear();
            foreach (XElement xProvider in xRoot.Elements())
            {
                ProviderInfo providerInfo = ProviderInfo.CreateFrom(xProvider);
                if (providerInfo != null)
                    this.Providers.Add(providerInfo);
            }

            this._SelectActiveProviderInfo();
        }
        private void _FileSave()
        {
            string fileName = this._FileName;
            if (String.IsNullOrEmpty(fileName)) return;
            XDocument xDoc = new XDocument();
            
            // Hlavička:
            XElement xRoot = new XElement("search_presets");
            xDoc.Add(xRoot);
            xRoot.SetAttributeValue("version", "1.0");
            xRoot.SetAttributeValue("active", this.ActiveProviderTypeName);

            // Jednotlivé presety:
            this.Providers.Sort(ProviderInfo.CompareByName);
            foreach (ProviderInfo providerInfo in this.Providers.Where(p => p.HasPresets))
                xRoot.Add(providerInfo.CreateXElement());

            xDoc.Save(fileName);
        }
        #endregion
        #region Provider - typ, jméno, aktivace
        /// <summary>
        /// Aktivní provider. Lze pouze setovat. Setování vloží hodnotu Type.Namespace + . + Name do ActiveProviderTypeName.
        /// </summary>
        internal object ActiveProvider { set { this.ActiveProviderTypeName = TypeName(value); } }
        /// <summary>
        /// Namespace + Name aktivního provideru
        /// </summary>
        internal string ActiveProviderTypeName
        {
            get { return this._ActiveProviderTypeName; }
            set
            {
                if (!String.Equals(this._ActiveProviderTypeName, value, StringComparison.InvariantCulture))
                {
                    this._ActiveProviderTypeName = value;
                    this._SelectActiveProviderInfo();
                    this._FileSave();
                }
            }
        }
        /// <summary>
        /// Nastaví do this._ActiveProviderInfo info o presetech aktuálního provideru (this._ActiveProviderTypeName).
        /// </summary>
        private void _SelectActiveProviderInfo()
        {
            this._ActiveProviderInfo = this._FindProviderInfo(this._ActiveProviderTypeName);
        }
        /// <summary>
        /// Najde a vrátí informace o presetech provideru daného jména.
        /// Pokud je dosud nemá, pak je založí jako nový záznam do this.Presets
        /// </summary>
        /// <param name="providerTypeName"></param>
        /// <returns></returns>
        private ProviderInfo _FindProviderInfo(string providerTypeName)
        {
            ProviderInfo providerInfo = null;
            if (!String.IsNullOrEmpty(providerTypeName))
            {
                providerInfo = this.Providers.FirstOrDefault(p => p.ProviderTypeName == providerTypeName);
                if (providerInfo == null)
                {
                    providerInfo = new ProviderInfo(providerTypeName);
                    this.Providers.Add(providerInfo);
                }
            }
            return providerInfo;
        }
        /// <summary>
        /// Příznak true, pokud v objektu je vybrán aktivní provider.
        /// </summary>
        internal bool HasActiveProviderInfo { get { return (this._ActiveProviderInfo != null); } }
        /// <summary>
        /// Aktivní provider, jeho data presetů.
        /// </summary>
        protected ProviderInfo ActiveProviderInfo { get { return this._ActiveProviderInfo; } }
        /// <summary>
        /// Vrátí true, pokud daný objekt je téhož typu (Namespace + Name), jaký je uložen v this.ActiveProviderTypeName
        /// </summary>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal bool IsActiveProvider(object provider)
        {
            if (provider == null) return false;
            string name = this.ActiveProviderTypeName;
            if (String.IsNullOrEmpty(name)) return false;
            return (TypeName(provider) == name);
        }
        /// <summary>
        /// Vrátí Namespace + Name z typ dodaného objektu. Pokud je dodán objekt null, vrátí "".
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        internal static string TypeName(object o)
        {
            if (o == null) return "";
            Type type = o.GetType();
            return type.Namespace + "." + type.Name;
        }
        #endregion
        #region Presety aktivního providera
        /// <summary>
        /// Obsahuje položky presetů do nabídky
        /// </summary>
        internal IEnumerable<DComboItem> ActiveProviderPresetItems
        {
            get
            {
                List<DComboItem> items = new List<DComboItem>();

                if (this.HasActiveProviderInfo)
                    items.AddRange(this.ActiveProviderInfo.PresetItems);

                return items;
            }
        }
        /// <summary>
        /// Vrátí data presetu z dané položky Combo.
        /// Proč: třída SearchPersistor ve své property ActiveProviderPresetItems vytváří prvky DComboItem, do nich vkládá data presetů.
        /// Ať si tedy tato třída data presetů z položky DComboItem zase vytáhne.
        /// </summary>
        /// <param name="dItem"></param>
        /// <returns></returns>
        internal static XElement GetPresetData(DComboItem dItem)
        {
            return PresetInfo.GetPresetData(dItem);
        }
        /// <summary>
        /// Do dat aktivního search providera uloží preset daného jména.
        /// Pokud název existuje, přepíše se. Pokud neexistuje, založíme nový.
        /// </summary>
        /// <param name="presetName"></param>
        /// <param name="presetData"></param>
        internal bool SetPreset(string presetName, XElement presetData)
        {
            if (!this.HasActiveProviderInfo) return false;
            bool result = this.ActiveProviderInfo.SetPreset(presetName, presetData);
            this._FileSave();
            return result;
        }
        #endregion
        #region Vlastní persistovaná data : souhrn providerů a jejich data (subclass ProviderInfo a PresetInfo)
        /// <summary>
        /// Soupis providerů a jejich uložené presety
        /// </summary>
        protected List<ProviderInfo> Providers { get { return this._Providers; } }
        /// <summary>
        /// Data jednoho providera
        /// </summary>
        protected class ProviderInfo
        {
            internal ProviderInfo(string providerTypeName)
            {
                this.ProviderTypeName = providerTypeName;
                this.PresetList = new List<PresetInfo>();
            }
            /// <summary>
            /// Název typu providera hledání
            /// </summary>
            internal string ProviderTypeName { get; set; }
            /// <summary>
            /// Jednotlivé presety tohoto providera
            /// </summary>
            internal List<PresetInfo> PresetList { get; private set; }
            /// <summary>
            /// Obsahuje položky presetů do nabídky
            /// </summary>
            internal IEnumerable<DComboItem> PresetItems
            {
                get
                {
                    List<DComboItem> items = new List<DComboItem>();
                    foreach (PresetInfo presetInfo in this.PresetList)
                        items.Add(presetInfo.PresetItem);
                    return items;
                }
            }
            /// <summary>
            /// Do dat tohoto search providera uloží preset daného jména
            /// </summary>
            /// <param name="presetName"></param>
            /// <param name="presetData"></param>
            internal bool SetPreset(string presetName, XElement presetData)
            {
                if (String.IsNullOrEmpty(presetName) || presetData == null) return false;

                PresetInfo presetInfo = this.PresetList.FirstOrDefault(p => p.PresetName == presetName);
                if (presetInfo == null)
                {
                    presetInfo = new PresetInfo();
                    presetInfo.PresetName = presetName;
                    this.PresetList.Add(presetInfo);
                }
                presetInfo.PresetData = presetData;
                return true;
            }
            /// <summary>
            /// Příznak true, pokud tento provider má nějaký (alespoň jeden) preset
            /// </summary>
            internal bool HasPresets { get { return (this.PresetList.Count > 0); } }
            /// <summary>
            /// Komparátor podle ProviderTypeName, vhodný pro Sort
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            internal static int CompareByName(ProviderInfo a, ProviderInfo b)
            {
                if (a == null && b == null) return 0;
                if (a == null) return -1;
                if (b == null) return 1;
                return String.Compare(a.ProviderTypeName, b.ProviderTypeName);
            }
            /// <summary>
            /// Vrátí XML element za tohoto providera.
            /// Element má název "provider", atribut "name=(ProviderTypeName)", a obsahuje vnořené elementy za každý preset.
            /// </summary>
            /// <returns></returns>
            internal XElement CreateXElement()
            {
                if (!this.HasPresets) return null;
                XElement xElement = new XElement("provider");
                xElement.Add(new XAttribute("name", this.ProviderTypeName));

                foreach (PresetInfo presetInfo in this.PresetList.Where(p => p.HasData))
                    xElement.Add(presetInfo.CreateXElement());

                return xElement;
            }
            /// <summary>
            /// Vytvoří nový objekt ProviderInfo z dat v dodaném XElementu.
            /// Pokud je element vadný, vrací null.
            /// </summary>
            /// <param name="xProvider"></param>
            /// <returns></returns>
            internal static ProviderInfo CreateFrom(XElement xProvider)
            {
                if (xProvider == null) return null;
                if (xProvider.Name.LocalName != "provider") return null;
                
                XAttribute xName = xProvider.Attribute("name");
                if (xName == null || String.IsNullOrEmpty(xName.Value)) return null;
                ProviderInfo providerInfo = new ProviderInfo(xName.Value.Trim());

                foreach (XElement xPreset in xProvider.Elements())
                {
                    PresetInfo presetInfo = PresetInfo.CreateFrom(xPreset);
                    if (presetInfo != null)
                        providerInfo.PresetList.Add(presetInfo);
                }

                if (!providerInfo.HasPresets) return null;
                return providerInfo;
            }
        }
        /// <summary>
        /// Data jednoho presetu
        /// </summary>
        protected class PresetInfo
        {
            /// <summary>
            /// Název presetu = předvolby hledání.
            /// Název se zobrazuje v combo boxu "Předvolby"
            /// </summary>
            internal string PresetName { get; set; }
            /// <summary>
            /// Data presetu = informace z controlu (IDataPersistent.PersistentData).
            /// Data popisují hodnoty jednotlivých prvků.
            /// </summary>
            internal XElement PresetData { get; set; }
            /// <summary>
            /// Vrací objekt do nabídky DComboBox, který představuje tento preset (název + XML data).
            /// </summary>
            internal DComboItem PresetItem
            {
                get { return new DComboItem(this.PresetName, this.PresetData); }
            }
            /// <summary>
            /// Vrátí data presetu z dané položky Combo.
            /// Proč: třída PresetInfo ve své property PresetItem vytváří prvek DComboItem, do něj vkládá svoje data presetu.
            /// Ať si tedy tato třída tato data z položky DComboItem zase vytáhne.
            /// </summary>
            /// <param name="dItem"></param>
            /// <returns></returns>
            internal static XElement GetPresetData(DComboItem dItem)
            {
                return (dItem == null ? (XElement)null : dItem.Data as XElement);
            }
            /// <summary>
            /// Příznak true, pokud tento preset má název i data
            /// </summary>
            internal bool HasData { get { return (!String.IsNullOrEmpty(this.PresetName) && this.PresetData != null); } }
            /// <summary>
            /// Vrátí XML element za tento preset.
            /// Element má název "preset", atribut "name=(PresetName)", a obsahuje data (this.PresetData).
            /// </summary>
            /// <returns></returns>
            internal XElement CreateXElement()
            {
                if (!this.HasData) return null;
                XElement xElement = new XElement("preset");
                xElement.Add(new XAttribute("name", this.PresetName));
                xElement.Add(this.PresetData);
                return xElement;
            }
            /// <summary>
            /// Vytvoří nový objekt PresetInfo z dat v dodaném XElementu.
            /// Pokud je element vadný, vrací null.
            /// </summary>
            /// <param name="xPreset"></param>
            /// <returns></returns>
            internal static PresetInfo CreateFrom(XElement xPreset)
            {
                if (xPreset == null) return null;
                if (xPreset.Name.LocalName != "preset") return null;

                XAttribute xName = xPreset.Attribute("name");
                if (xName == null || String.IsNullOrEmpty(xName.Value)) return null;
                if (!xPreset.HasElements) return null;
                XElement xData = xPreset.Elements().FirstOrDefault();

                PresetInfo presetInfo = new PresetInfo();
                presetInfo.PresetName = xName.Value;
                presetInfo.PresetData = xData;

                return presetInfo;
            }
        }
        #endregion
    }
    #endregion
    #region class SearchTestItemString, SearchTestItem : objekty pro podporu vyhledávání konkrétního údaje
    /// <summary>
    /// SearchTestItemString : objekt pro podporu vyhledávání stringového údaje pomocí RegEx
    /// </summary>
    public class SearchTestItemRegEx : SearchTestItemString
    {
        public SearchTestItemRegEx() { }
        public SearchTestItemRegEx(string value) : base(value) { }
        public override bool TestContain(string value)
        {
            if (!this.IsTested) return true;
            if (value == null) return false;
            if (value.ToLower().Contains(this.TestValue)) return true;
            return this.Regex.IsMatch(value);
        }
        public override string TestValue
        {
            get
            {
                return base.TestValue;
            }
            protected set
            {
                base.TestValue = null;
                this.IsTested = (value != null);
                if (this.IsTested)
                {
                    base.TestValue = value;
                    string pattern = value.Replace("\r", "").Replace("\n", "");
                    this.Regex = new System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace);
                }
            }
        }
        protected System.Text.RegularExpressions.Regex Regex;
    }
    /// <summary>
    /// SearchTestItemString : objekt pro podporu vyhledávání stringového údaje, bez RegEx
    /// </summary>
    public class SearchTestItemString : SearchTestItem<string>
    {
        public SearchTestItemString() { }
        public SearchTestItemString(string value) : base(value) { }
        public virtual bool TestContain(string value)
        {
            if (!this.IsTested) return true;
            if (value == null) return false;
            return value.ToLower().Contains(this.TestValue);
        }
        public override string TestValue
        {
            get
            {
                return base.TestValue;
            }
            protected set
            {
                base.TestValue = (value == null ? null : value.ToLower());
                this.IsTested = (value != null);
            }
        }
    }
    /// <summary>
    /// SearchTestItem : objekt pro podporu vyhledávání údaje, který je IComparable
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SearchTestItem<T> where T : IComparable
    {
        public SearchTestItem()
        {
            this.IsTested = false;
        }
        public SearchTestItem(T value)
        {
            this.IsTested = true;
            this.TestValue = value;
        }
        public override string ToString()
        {
            return base.ToString();
        }
        public bool IsTested { get; protected set; }
        public virtual T TestValue { get; protected set; }
        /// <summary>
        /// Testuje danou hodnotu proti zdejší hodnotě, zda jsou shodné.
        /// Vrací true, pokud daná hodnota je rovna zdejší hodnota.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual bool TestEqual(IComparable value)
        {
            return (!this.IsTested || this.TestValue.CompareTo(value) == 0);
        }
        /// <summary>
        /// Testuje danou hodnotu proti zdejší hodnotě, která představuje minimální hranici.
        /// Vrací true, pokud daná hodnota je rovna nebo vyšší, než zdejší hodnota.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual bool TestMin(IComparable value)
        {
            return (!this.IsTested || this.TestValue.CompareTo(value) <= 0);
        }
        /// <summary>
        /// Testuje danou hodnotu proti zdejší hodnotě, která představuje maximální hranici.
        /// Vrací true, pokud daná hodnota je rovna nebo nižší, než zdejší hodnota.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual bool TestMax(IComparable value)
        {
            return (!this.IsTested || this.TestValue.CompareTo(value) >= 0);
        }
    }
    #endregion
    #region enum SearchState
    /// <summary>
    /// Stav procesu hledání
    /// </summary>
    internal enum SearchState
    {
        /// <summary>
        /// Proces hledání neběží, je Stopped
        /// </summary>
        None = 0,
        /// <summary>
        /// Proces hledání běží, je Running
        /// </summary>
        Running,
        /// <summary>
        /// Proces hledání je pozastaven, je Paused
        /// </summary>
        Paused
    }
    #endregion
}
